{
  "nbformat": 4,
  "nbformat_minor": 0,
  "metadata": {
    "colab": {
      "provenance": [],
      "toc_visible": true,
      "authorship_tag": "ABX9TyNw8wyHU1lV16u12yYhynd4",
      "include_colab_link": true
    },
    "kernelspec": {
      "name": "python3",
      "display_name": "Python 3"
    },
    "language_info": {
      "name": "python"
    }
  },
  "cells": [
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "view-in-github",
        "colab_type": "text"
      },
      "source": [
        "<a href=\"https://colab.research.google.com/github/chenyu313/TensorFlow-note/blob/main/TensorFlow%E5%86%B3%E7%AD%96%E6%A3%AE%E6%9E%97.ipynb\" target=\"_parent\"><img src=\"https://colab.research.google.com/assets/colab-badge.svg\" alt=\"Open In Colab\"/></a>"
      ]
    },
    {
      "cell_type": "markdown",
      "source": [
        "## 使用TensorFlow决策森林构建、训练和评估模型\n",
        "决策森林(DF)是一系列用于监督分类、回归和排序的机器学习算法。顾名思义，DF使用决策树作为构建块。目前，两种最流行的DF训练算法是随机森林和梯度增强决策树。\n",
        "\n",
        "TensorFlow Decision Forests (TF-DF)是一个用于训练、评估、解释和推理决策森林模型的库。\n"
      ],
      "metadata": {
        "id": "Z2xNW-cmgc6B"
      }
    },
    {
      "cell_type": "markdown",
      "source": [
        "## 安装TensorFlow决策森林"
      ],
      "metadata": {
        "id": "xFks9g-rgjE3"
      }
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "AAoMWsXLgH5j"
      },
      "outputs": [],
      "source": [
        "!pip install tensorflow_decision_forests\n",
        "# 需要Wurlitzer在Colabs中显示详细的训练日志(当在模型构造器中使用verbose=2时)。\n",
        "!pip install wurlitzer"
      ]
    },
    {
      "cell_type": "code",
      "source": [
        "import tensorflow_decision_forests as tfdf\n",
        "\n",
        "import os\n",
        "import numpy as np\n",
        "import pandas as pd\n",
        "import tensorflow as tf\n",
        "import math"
      ],
      "metadata": {
        "id": "w3CuyhoBhWHg"
      },
      "execution_count": 2,
      "outputs": []
    },
    {
      "cell_type": "code",
      "source": [
        "# 检查TensorFlow决策森林的版本\n",
        "print(\"Found TensorFlow Decision Forests v\" + tfdf.__version__)"
      ],
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "id": "IFf1RfB3hcOI",
        "outputId": "abdd7efa-4d82-4168-dfc8-1b1163d96f79"
      },
      "execution_count": 3,
      "outputs": [
        {
          "output_type": "stream",
          "name": "stdout",
          "text": [
            "Found TensorFlow Decision Forests v1.3.0\n"
          ]
        }
      ]
    },
    {
      "cell_type": "markdown",
      "source": [
        "## 训练随机森林模型\n",
        "训练、评估、分析和导出一个基于Palmer’s Penguins数据集训练的二元分类随机森林。\n",
        "\n",
        "注:数据集导出为csv文件，未进行预处理:library(palmerpenguins)；write.csv(penguins，file=\"penguins.csv\"， quote=F, row.names=F)。"
      ],
      "metadata": {
        "id": "tapcP1L0huF3"
      }
    },
    {
      "cell_type": "markdown",
      "source": [
        "### 加载数据集并将其转换为tf.Dataset\n",
        "这个数据集非常小(300个示例)，并存储为.csv类文件。因此，使用Pandas来加载它。\n",
        "\n",
        "让我们将数据集组装成一个csv文件(即添加标题)，并加载它:\n",
        "\n",
        "这是一个帕尔马企鹅数据集，其中包含三种企鹅物种的测量值：\n",
        "* Chinstrap\n",
        "* Gentoo\n",
        "* Adelie\n",
        "\n",
        "![image.png]()"
      ],
      "metadata": {
        "id": "Bu6ngVcajL8I"
      }
    },
    {
      "cell_type": "code",
      "source": [
        "# 下载数据集\n",
        "!wget -q https://storage.googleapis.com/download.tensorflow.org/data/palmer_penguins/penguins.csv -O /tmp/penguins.csv\n",
        "\n",
        "# 将数据集加载到Pandas Dataframe\n",
        "dataset_df = pd.read_csv(\"/tmp/penguins.csv\")\n",
        "\n",
        "# 显示前3个示例\n",
        "dataset_df.head(3)"
      ],
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/",
          "height": 143
        },
        "id": "DzbR2Gnoh5-H",
        "outputId": "0a196c50-13ce-453f-b403-8126d2cb52a1"
      },
      "execution_count": 15,
      "outputs": [
        {
          "output_type": "execute_result",
          "data": {
            "text/plain": [
              "  species     island  bill_length_mm  bill_depth_mm  flipper_length_mm  \\\n",
              "0  Adelie  Torgersen            39.1           18.7              181.0   \n",
              "1  Adelie  Torgersen            39.5           17.4              186.0   \n",
              "2  Adelie  Torgersen            40.3           18.0              195.0   \n",
              "\n",
              "   body_mass_g     sex  year  \n",
              "0       3750.0    male  2007  \n",
              "1       3800.0  female  2007  \n",
              "2       3250.0  female  2007  "
            ],
            "text/html": [
              "\n",
              "  <div id=\"df-afe59126-06a6-4763-867f-e78f87ef600d\">\n",
              "    <div class=\"colab-df-container\">\n",
              "      <div>\n",
              "<style scoped>\n",
              "    .dataframe tbody tr th:only-of-type {\n",
              "        vertical-align: middle;\n",
              "    }\n",
              "\n",
              "    .dataframe tbody tr th {\n",
              "        vertical-align: top;\n",
              "    }\n",
              "\n",
              "    .dataframe thead th {\n",
              "        text-align: right;\n",
              "    }\n",
              "</style>\n",
              "<table border=\"1\" class=\"dataframe\">\n",
              "  <thead>\n",
              "    <tr style=\"text-align: right;\">\n",
              "      <th></th>\n",
              "      <th>species</th>\n",
              "      <th>island</th>\n",
              "      <th>bill_length_mm</th>\n",
              "      <th>bill_depth_mm</th>\n",
              "      <th>flipper_length_mm</th>\n",
              "      <th>body_mass_g</th>\n",
              "      <th>sex</th>\n",
              "      <th>year</th>\n",
              "    </tr>\n",
              "  </thead>\n",
              "  <tbody>\n",
              "    <tr>\n",
              "      <th>0</th>\n",
              "      <td>Adelie</td>\n",
              "      <td>Torgersen</td>\n",
              "      <td>39.1</td>\n",
              "      <td>18.7</td>\n",
              "      <td>181.0</td>\n",
              "      <td>3750.0</td>\n",
              "      <td>male</td>\n",
              "      <td>2007</td>\n",
              "    </tr>\n",
              "    <tr>\n",
              "      <th>1</th>\n",
              "      <td>Adelie</td>\n",
              "      <td>Torgersen</td>\n",
              "      <td>39.5</td>\n",
              "      <td>17.4</td>\n",
              "      <td>186.0</td>\n",
              "      <td>3800.0</td>\n",
              "      <td>female</td>\n",
              "      <td>2007</td>\n",
              "    </tr>\n",
              "    <tr>\n",
              "      <th>2</th>\n",
              "      <td>Adelie</td>\n",
              "      <td>Torgersen</td>\n",
              "      <td>40.3</td>\n",
              "      <td>18.0</td>\n",
              "      <td>195.0</td>\n",
              "      <td>3250.0</td>\n",
              "      <td>female</td>\n",
              "      <td>2007</td>\n",
              "    </tr>\n",
              "  </tbody>\n",
              "</table>\n",
              "</div>\n",
              "      <button class=\"colab-df-convert\" onclick=\"convertToInteractive('df-afe59126-06a6-4763-867f-e78f87ef600d')\"\n",
              "              title=\"Convert this dataframe to an interactive table.\"\n",
              "              style=\"display:none;\">\n",
              "        \n",
              "  <svg xmlns=\"http://www.w3.org/2000/svg\" height=\"24px\"viewBox=\"0 0 24 24\"\n",
              "       width=\"24px\">\n",
              "    <path d=\"M0 0h24v24H0V0z\" fill=\"none\"/>\n",
              "    <path d=\"M18.56 5.44l.94 2.06.94-2.06 2.06-.94-2.06-.94-.94-2.06-.94 2.06-2.06.94zm-11 1L8.5 8.5l.94-2.06 2.06-.94-2.06-.94L8.5 2.5l-.94 2.06-2.06.94zm10 10l.94 2.06.94-2.06 2.06-.94-2.06-.94-.94-2.06-.94 2.06-2.06.94z\"/><path d=\"M17.41 7.96l-1.37-1.37c-.4-.4-.92-.59-1.43-.59-.52 0-1.04.2-1.43.59L10.3 9.45l-7.72 7.72c-.78.78-.78 2.05 0 2.83L4 21.41c.39.39.9.59 1.41.59.51 0 1.02-.2 1.41-.59l7.78-7.78 2.81-2.81c.8-.78.8-2.07 0-2.86zM5.41 20L4 18.59l7.72-7.72 1.47 1.35L5.41 20z\"/>\n",
              "  </svg>\n",
              "      </button>\n",
              "      \n",
              "  <style>\n",
              "    .colab-df-container {\n",
              "      display:flex;\n",
              "      flex-wrap:wrap;\n",
              "      gap: 12px;\n",
              "    }\n",
              "\n",
              "    .colab-df-convert {\n",
              "      background-color: #E8F0FE;\n",
              "      border: none;\n",
              "      border-radius: 50%;\n",
              "      cursor: pointer;\n",
              "      display: none;\n",
              "      fill: #1967D2;\n",
              "      height: 32px;\n",
              "      padding: 0 0 0 0;\n",
              "      width: 32px;\n",
              "    }\n",
              "\n",
              "    .colab-df-convert:hover {\n",
              "      background-color: #E2EBFA;\n",
              "      box-shadow: 0px 1px 2px rgba(60, 64, 67, 0.3), 0px 1px 3px 1px rgba(60, 64, 67, 0.15);\n",
              "      fill: #174EA6;\n",
              "    }\n",
              "\n",
              "    [theme=dark] .colab-df-convert {\n",
              "      background-color: #3B4455;\n",
              "      fill: #D2E3FC;\n",
              "    }\n",
              "\n",
              "    [theme=dark] .colab-df-convert:hover {\n",
              "      background-color: #434B5C;\n",
              "      box-shadow: 0px 1px 3px 1px rgba(0, 0, 0, 0.15);\n",
              "      filter: drop-shadow(0px 1px 2px rgba(0, 0, 0, 0.3));\n",
              "      fill: #FFFFFF;\n",
              "    }\n",
              "  </style>\n",
              "\n",
              "      <script>\n",
              "        const buttonEl =\n",
              "          document.querySelector('#df-afe59126-06a6-4763-867f-e78f87ef600d button.colab-df-convert');\n",
              "        buttonEl.style.display =\n",
              "          google.colab.kernel.accessAllowed ? 'block' : 'none';\n",
              "\n",
              "        async function convertToInteractive(key) {\n",
              "          const element = document.querySelector('#df-afe59126-06a6-4763-867f-e78f87ef600d');\n",
              "          const dataTable =\n",
              "            await google.colab.kernel.invokeFunction('convertToInteractive',\n",
              "                                                     [key], {});\n",
              "          if (!dataTable) return;\n",
              "\n",
              "          const docLinkHtml = 'Like what you see? Visit the ' +\n",
              "            '<a target=\"_blank\" href=https://colab.research.google.com/notebooks/data_table.ipynb>data table notebook</a>'\n",
              "            + ' to learn more about interactive tables.';\n",
              "          element.innerHTML = '';\n",
              "          dataTable['output_type'] = 'display_data';\n",
              "          await google.colab.output.renderOutput(dataTable, element);\n",
              "          const docLink = document.createElement('div');\n",
              "          docLink.innerHTML = docLinkHtml;\n",
              "          element.appendChild(docLink);\n",
              "        }\n",
              "      </script>\n",
              "    </div>\n",
              "  </div>\n",
              "  "
            ]
          },
          "metadata": {},
          "execution_count": 15
        }
      ]
    },
    {
      "cell_type": "markdown",
      "source": [
        "数据集包含数值特征(例如bill_depth_mm)、分类特征(例如island)和缺失特征的混合。TF-DF原生支持所有这些特征类型(与基于神经网络的模型不同)，因此不需要以one-hot编码、规范化或额外的is_present特征的形式进行预处理。\n",
        "\n",
        "标签有点不同:Keras指标需要整数。标签(物种)存储为字符串，因此让我们将其转换为整数。"
      ],
      "metadata": {
        "id": "rf6P8oTLkSs3"
      }
    },
    {
      "cell_type": "code",
      "source": [
        "# 将分类标签编码为整数\n",
        "#\n",
        "# 细节:\n",
        "# 如果分类标签表示为字符串，这个阶段是必要的，因为Keras需要整数分类标签.\n",
        "# 当使用' pd_dataframe_to_tf_dataset '(见下文)时，可以跳过此步骤\n",
        "\n",
        "# 标签列名称\n",
        "label = \"species\"\n",
        "\n",
        "classes = dataset_df[label].unique().tolist()\n",
        "print(f\"Label classes: {classes}\")\n",
        "\n",
        "dataset_df[label] = dataset_df[label].map(classes.index)"
      ],
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "id": "WOmCzgVHkgrf",
        "outputId": "06a7b248-f3e3-4fbb-c5f7-5e771334c797"
      },
      "execution_count": 5,
      "outputs": [
        {
          "output_type": "stream",
          "name": "stdout",
          "text": [
            "Label classes: ['Adelie', 'Gentoo', 'Chinstrap']\n"
          ]
        }
      ]
    },
    {
      "cell_type": "markdown",
      "source": [
        "接下来将数据集分成训练和测试两部分:"
      ],
      "metadata": {
        "id": "nQKk4ihGtsrX"
      }
    },
    {
      "cell_type": "code",
      "source": [
        "def split_dataset(dataset, test_ratio=0.30):\n",
        "  \"\"\"Splits a panda dataframe in two.\"\"\"\n",
        "  test_indices = np.random.rand(len(dataset)) < test_ratio\n",
        "  return dataset[~test_indices], dataset[test_indices]\n",
        "\n",
        "\n",
        "train_ds_pd, test_ds_pd = split_dataset(dataset_df)\n",
        "print(\"{} examples in training, {} examples for testing.\".format(\n",
        "    len(train_ds_pd), len(test_ds_pd)))"
      ],
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "id": "RixpdTumttzm",
        "outputId": "e8f6d51b-2424-4174-d026-cd31ba317040"
      },
      "execution_count": 6,
      "outputs": [
        {
          "output_type": "stream",
          "name": "stdout",
          "text": [
            "250 examples in training, 94 examples for testing.\n"
          ]
        }
      ]
    },
    {
      "cell_type": "markdown",
      "source": [
        "最后，将pandas数据框(pd.Dataframe)转换为tensorflow数据集(tf.data.Dataset):"
      ],
      "metadata": {
        "id": "MUm_JrJDupAl"
      }
    },
    {
      "cell_type": "code",
      "source": [
        "train_ds = tfdf.keras.pd_dataframe_to_tf_dataset(train_ds_pd, label=label)\n",
        "test_ds = tfdf.keras.pd_dataframe_to_tf_dataset(test_ds_pd, label=label)"
      ],
      "metadata": {
        "id": "RLK3yOPXupvm"
      },
      "execution_count": 7,
      "outputs": []
    },
    {
      "cell_type": "markdown",
      "source": [
        "注意:回想一下pd_dataframe_to_tf_dataset在必要时将字符串标签转换为整数。\n",
        "如果你想自己创建tf.data.Dataset，有几件事要记住:\n",
        "* 该学习算法使用单历元数据集，无需随机打乱。\n",
        "* 批处理大小不会影响训练算法，但较小的值可能会减慢读取数据集的速度。"
      ],
      "metadata": {
        "id": "Z_D1p2-mvF6F"
      }
    },
    {
      "cell_type": "markdown",
      "source": [
        "### 使用默认超参数训练模型\n"
      ],
      "metadata": {
        "id": "iEdUwHlevX2W"
      }
    },
    {
      "cell_type": "markdown",
      "source": [
        "可以在不指定任何超参数的情况下训练第一个 CART（分类树和回归树）模型。这是因为 tfdf.keras.CartModel 函数提供良好的默认超参数值。在本课程的后面部分，您将详细了解此类模型的工作原理。"
      ],
      "metadata": {
        "id": "N4pYj7XIN6qn"
      }
    },
    {
      "cell_type": "code",
      "source": [
        "\n",
        "model = tfdf.keras.CartModel()\n",
        "model.fit(train_ds)"
      ],
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "id": "a69RBIXNN5Hm",
        "outputId": "0f876d0e-1cfb-4eec-e3bc-15ee39aac681"
      },
      "execution_count": 18,
      "outputs": [
        {
          "output_type": "stream",
          "name": "stdout",
          "text": [
            "Use /tmp/tmpea3unavs as temporary training directory\n",
            "Reading training dataset...\n",
            "Training dataset read in 0:00:00.204938. Found 250 examples.\n",
            "Training model...\n",
            "Model trained in 0:00:00.022208\n",
            "Compiling model...\n",
            "Model compiled.\n"
          ]
        },
        {
          "output_type": "execute_result",
          "data": {
            "text/plain": [
              "<keras.callbacks.History at 0x7fd2578147f0>"
            ]
          },
          "metadata": {},
          "execution_count": 18
        }
      ]
    },
    {
      "cell_type": "markdown",
      "source": [
        "上述对 tfdf.keras.CartModel 的调用没有指定用作输入特征的列。因此，系统会使用训练集中的每一列。该调用也未指定输入特征的语义（例如，数值、分类、文本）。因此，tfdf.keras.CartModel 会自动推断语义。\n",
        "\n",
        "调用 tfdf.model_plotter.plot_model_in_colab 以显示生成的决策树："
      ],
      "metadata": {
        "id": "bZi38135OY3f"
      }
    },
    {
      "cell_type": "code",
      "source": [
        "tfdf.model_plotter.plot_model_in_colab(model, max_depth=10)"
      ],
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/",
          "height": 205
        },
        "id": "vjLGpItJOb6e",
        "outputId": "bf6e0b86-abd4-43b0-81c2-d737d631ddcb"
      },
      "execution_count": 19,
      "outputs": [
        {
          "output_type": "execute_result",
          "data": {
            "text/plain": [
              "<IPython.core.display.HTML object>"
            ],
            "text/html": [
              "\n",
              "<script src=\"https://d3js.org/d3.v6.min.js\"></script>\n",
              "<div id=\"tree_plot_0aef01c97aa1475caa49cf8309ac7270\"></div>\n",
              "<script>\n",
              "/*\n",
              " * Copyright 2021 Google LLC.\n",
              " * Licensed under the Apache License, Version 2.0 (the \"License\");\n",
              " * you may not use this file except in compliance with the License.\n",
              " * You may obtain a copy of the License at\n",
              " *\n",
              " *     https://www.apache.org/licenses/LICENSE-2.0\n",
              " *\n",
              " * Unless required by applicable law or agreed to in writing, software\n",
              " * distributed under the License is distributed on an \"AS IS\" BASIS,\n",
              " * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n",
              " * See the License for the specific language governing permissions and\n",
              " * limitations under the License.\n",
              " */\n",
              "\n",
              "/**\n",
              " *  Plotting of decision trees generated by TF-DF.\n",
              " *\n",
              " *  A tree is a recursive structure of node objects.\n",
              " *  A node contains one or more of the following components:\n",
              " *\n",
              " *    - A value: Representing the output of the node. If the node is not a leaf,\n",
              " *      the value is only present for analysis i.e. it is not used for\n",
              " *      predictions.\n",
              " *\n",
              " *    - A condition : For non-leaf nodes, the condition (also known as split)\n",
              " *      defines a binary test to branch to the positive or negative child.\n",
              " *\n",
              " *    - An explanation: Generally a plot showing the relation between the label\n",
              " *      and the condition to give insights about the effect of the condition.\n",
              " *\n",
              " *    - Two children : For non-leaf nodes, the children nodes. The first\n",
              " *      children (i.e. \"node.children[0]\") is the negative children (drawn in\n",
              " *      red). The second children is the positive one (drawn in green).\n",
              " *\n",
              " */\n",
              "\n",
              "/**\n",
              " * Plots a single decision tree into a DOM element.\n",
              " * @param {!options} options Dictionary of configurations.\n",
              " * @param {!tree} raw_tree Recursive tree structure.\n",
              " * @param {string} canvas_id Id of the output dom element.\n",
              " */\n",
              "function display_tree(options, raw_tree, canvas_id) {\n",
              "  console.log(options);\n",
              "\n",
              "  // Determine the node placement.\n",
              "  const tree_struct = d3.tree().nodeSize(\n",
              "      [options.node_y_offset, options.node_x_offset])(d3.hierarchy(raw_tree));\n",
              "\n",
              "  // Boundaries of the node placement.\n",
              "  let x_min = Infinity;\n",
              "  let x_max = -x_min;\n",
              "  let y_min = Infinity;\n",
              "  let y_max = -x_min;\n",
              "\n",
              "  tree_struct.each(d => {\n",
              "    if (d.x > x_max) x_max = d.x;\n",
              "    if (d.x < x_min) x_min = d.x;\n",
              "    if (d.y > y_max) y_max = d.y;\n",
              "    if (d.y < y_min) y_min = d.y;\n",
              "  });\n",
              "\n",
              "  // Size of the plot.\n",
              "  const width = y_max - y_min + options.node_x_size + options.margin * 2;\n",
              "  const height = x_max - x_min + options.node_y_size + options.margin * 2 +\n",
              "      options.node_y_offset - options.node_y_size;\n",
              "\n",
              "  const plot = d3.select(canvas_id);\n",
              "\n",
              "  // Tool tip\n",
              "  options.tooltip = plot.append('div')\n",
              "                        .attr('width', 100)\n",
              "                        .attr('height', 100)\n",
              "                        .style('padding', '4px')\n",
              "                        .style('background', '#fff')\n",
              "                        .style('box-shadow', '4px 4px 0px rgba(0,0,0,0.1)')\n",
              "                        .style('border', '1px solid black')\n",
              "                        .style('font-family', 'sans-serif')\n",
              "                        .style('font-size', options.font_size)\n",
              "                        .style('position', 'absolute')\n",
              "                        .style('z-index', '10')\n",
              "                        .attr('pointer-events', 'none')\n",
              "                        .style('display', 'none');\n",
              "\n",
              "  // Create canvas\n",
              "  const svg = plot.append('svg').attr('width', width).attr('height', height);\n",
              "  const graph =\n",
              "      svg.style('overflow', 'visible')\n",
              "          .append('g')\n",
              "          .attr('font-family', 'sans-serif')\n",
              "          .attr('font-size', options.font_size)\n",
              "          .attr(\n",
              "              'transform',\n",
              "              () => `translate(${options.margin},${\n",
              "                  - x_min + options.node_y_offset / 2 + options.margin})`);\n",
              "\n",
              "  // Plot bounding box.\n",
              "  if (options.show_plot_bounding_box) {\n",
              "    svg.append('rect')\n",
              "        .attr('width', width)\n",
              "        .attr('height', height)\n",
              "        .attr('fill', 'none')\n",
              "        .attr('stroke-width', 1.0)\n",
              "        .attr('stroke', 'black');\n",
              "  }\n",
              "\n",
              "  // Draw the edges.\n",
              "  display_edges(options, graph, tree_struct);\n",
              "\n",
              "  // Draw the nodes.\n",
              "  display_nodes(options, graph, tree_struct);\n",
              "}\n",
              "\n",
              "/**\n",
              " * Draw the nodes of the tree.\n",
              " * @param {!options} options Dictionary of configurations.\n",
              " * @param {!graph} graph D3 search handle containing the graph.\n",
              " * @param {!tree_struct} tree_struct Structure of the tree (node placement,\n",
              " *     data, etc.).\n",
              " */\n",
              "function display_nodes(options, graph, tree_struct) {\n",
              "  const nodes = graph.append('g')\n",
              "                    .selectAll('g')\n",
              "                    .data(tree_struct.descendants())\n",
              "                    .join('g')\n",
              "                    .attr('transform', d => `translate(${d.y},${d.x})`);\n",
              "\n",
              "  nodes.append('rect')\n",
              "      .attr('x', 0.5)\n",
              "      .attr('y', 0.5)\n",
              "      .attr('width', options.node_x_size)\n",
              "      .attr('height', options.node_y_size)\n",
              "      .attr('stroke', 'lightgrey')\n",
              "      .attr('stroke-width', 1)\n",
              "      .attr('fill', 'white')\n",
              "      .attr('y', -options.node_y_size / 2);\n",
              "\n",
              "  // Brackets on the right of condition nodes without children.\n",
              "  non_leaf_node_without_children =\n",
              "      nodes.filter(node => node.data.condition != null && node.children == null)\n",
              "          .append('g')\n",
              "          .attr('transform', `translate(${options.node_x_size},0)`);\n",
              "\n",
              "  non_leaf_node_without_children.append('path')\n",
              "      .attr('d', 'M0,0 C 10,0 0,10 10,10')\n",
              "      .attr('fill', 'none')\n",
              "      .attr('stroke-width', 1.0)\n",
              "      .attr('stroke', '#F00');\n",
              "\n",
              "  non_leaf_node_without_children.append('path')\n",
              "      .attr('d', 'M0,0 C 10,0 0,-10 10,-10')\n",
              "      .attr('fill', 'none')\n",
              "      .attr('stroke-width', 1.0)\n",
              "      .attr('stroke', '#0F0');\n",
              "\n",
              "  const node_content = nodes.append('g').attr(\n",
              "      'transform',\n",
              "      `translate(0,${options.node_padding - options.node_y_size / 2})`);\n",
              "\n",
              "  node_content.append(node => create_node_element(options, node));\n",
              "}\n",
              "\n",
              "/**\n",
              " * Creates the D3 content for a single node.\n",
              " * @param {!options} options Dictionary of configurations.\n",
              " * @param {!node} node Node to draw.\n",
              " * @return {!d3} D3 content.\n",
              " */\n",
              "function create_node_element(options, node) {\n",
              "  // Output accumulator.\n",
              "  let output = {\n",
              "    // Content to draw.\n",
              "    content: d3.create('svg:g'),\n",
              "    // Vertical offset to the next element to draw.\n",
              "    vertical_offset: 0\n",
              "  };\n",
              "\n",
              "  // Conditions.\n",
              "  if (node.data.condition != null) {\n",
              "    display_condition(options, node.data.condition, output);\n",
              "  }\n",
              "\n",
              "  // Values.\n",
              "  if (node.data.value != null) {\n",
              "    display_value(options, node.data.value, output);\n",
              "  }\n",
              "\n",
              "  // Explanations.\n",
              "  if (node.data.explanation != null) {\n",
              "    display_explanation(options, node.data.explanation, output);\n",
              "  }\n",
              "\n",
              "  return output.content.node();\n",
              "}\n",
              "\n",
              "\n",
              "/**\n",
              " * Adds a single line of text inside of a node.\n",
              " * @param {!options} options Dictionary of configurations.\n",
              " * @param {string} text Text to display.\n",
              " * @param {!output} output Output display accumulator.\n",
              " */\n",
              "function display_node_text(options, text, output) {\n",
              "  output.content.append('text')\n",
              "      .attr('x', options.node_padding)\n",
              "      .attr('y', output.vertical_offset)\n",
              "      .attr('alignment-baseline', 'hanging')\n",
              "      .text(text);\n",
              "  output.vertical_offset += 10;\n",
              "}\n",
              "\n",
              "/**\n",
              " * Adds a single line of text inside of a node with a tooltip.\n",
              " * @param {!options} options Dictionary of configurations.\n",
              " * @param {string} text Text to display.\n",
              " * @param {string} tooltip Text in the Tooltip.\n",
              " * @param {!output} output Output display accumulator.\n",
              " */\n",
              "function display_node_text_with_tooltip(options, text, tooltip, output) {\n",
              "  const item = output.content.append('text')\n",
              "                   .attr('x', options.node_padding)\n",
              "                   .attr('alignment-baseline', 'hanging')\n",
              "                   .text(text);\n",
              "\n",
              "  add_tooltip(options, item, () => tooltip);\n",
              "  output.vertical_offset += 10;\n",
              "}\n",
              "\n",
              "/**\n",
              " * Adds a tooltip to a dom element.\n",
              " * @param {!options} options Dictionary of configurations.\n",
              " * @param {!dom} target Dom element to equip with a tooltip.\n",
              " * @param {!func} get_content Generates the html content of the tooltip.\n",
              " */\n",
              "function add_tooltip(options, target, get_content) {\n",
              "  function show(d) {\n",
              "    options.tooltip.style('display', 'block');\n",
              "    options.tooltip.html(get_content());\n",
              "  }\n",
              "\n",
              "  function hide(d) {\n",
              "    options.tooltip.style('display', 'none');\n",
              "  }\n",
              "\n",
              "  function move(d) {\n",
              "    options.tooltip.style('display', 'block');\n",
              "    options.tooltip.style('left', (d.pageX + 5) + 'px');\n",
              "    options.tooltip.style('top', d.pageY + 'px');\n",
              "  }\n",
              "\n",
              "  target.on('mouseover', show);\n",
              "  target.on('mouseout', hide);\n",
              "  target.on('mousemove', move);\n",
              "}\n",
              "\n",
              "/**\n",
              " * Adds a condition inside of a node.\n",
              " * @param {!options} options Dictionary of configurations.\n",
              " * @param {!condition} condition Condition to display.\n",
              " * @param {!output} output Output display accumulator.\n",
              " */\n",
              "function display_condition(options, condition, output) {\n",
              "  threshold_format = d3.format('r');\n",
              "\n",
              "  if (condition.type === 'IS_MISSING') {\n",
              "    display_node_text(options, `${condition.attribute} is missing`, output);\n",
              "    return;\n",
              "  }\n",
              "\n",
              "  if (condition.type === 'IS_TRUE') {\n",
              "    display_node_text(options, `${condition.attribute} is true`, output);\n",
              "    return;\n",
              "  }\n",
              "\n",
              "  if (condition.type === 'NUMERICAL_IS_HIGHER_THAN') {\n",
              "    format = d3.format('r');\n",
              "    display_node_text(\n",
              "        options,\n",
              "        `${condition.attribute} >= ${threshold_format(condition.threshold)}`,\n",
              "        output);\n",
              "    return;\n",
              "  }\n",
              "\n",
              "  if (condition.type === 'CATEGORICAL_IS_IN') {\n",
              "    display_node_text_with_tooltip(\n",
              "        options, `${condition.attribute} in [...]`,\n",
              "        `${condition.attribute} in [${condition.mask}]`, output);\n",
              "    return;\n",
              "  }\n",
              "\n",
              "  if (condition.type === 'CATEGORICAL_SET_CONTAINS') {\n",
              "    display_node_text_with_tooltip(\n",
              "        options, `${condition.attribute} intersect [...]`,\n",
              "        `${condition.attribute} intersect [${condition.mask}]`, output);\n",
              "    return;\n",
              "  }\n",
              "\n",
              "  if (condition.type === 'NUMERICAL_SPARSE_OBLIQUE') {\n",
              "    display_node_text_with_tooltip(\n",
              "        options, `Sparse oblique split...`,\n",
              "        `[${condition.attributes}]*[${condition.weights}]>=${\n",
              "            threshold_format(condition.threshold)}`,\n",
              "        output);\n",
              "    return;\n",
              "  }\n",
              "\n",
              "  display_node_text(\n",
              "      options, `Non supported condition ${condition.type}`, output);\n",
              "}\n",
              "\n",
              "/**\n",
              " * Adds a value inside of a node.\n",
              " * @param {!options} options Dictionary of configurations.\n",
              " * @param {!value} value Value to display.\n",
              " * @param {!output} output Output display accumulator.\n",
              " */\n",
              "function display_value(options, value, output) {\n",
              "  if (value.type === 'PROBABILITY') {\n",
              "    const left_margin = 0;\n",
              "    const right_margin = 50;\n",
              "    const plot_width = options.node_x_size - options.node_padding * 2 -\n",
              "        left_margin - right_margin;\n",
              "\n",
              "    let cusum = Array.from(d3.cumsum(value.distribution));\n",
              "    cusum.unshift(0);\n",
              "    const distribution_plot = output.content.append('g').attr(\n",
              "        'transform', `translate(0,${output.vertical_offset + 0.5})`);\n",
              "\n",
              "    distribution_plot.selectAll('rect')\n",
              "        .data(value.distribution)\n",
              "        .join('rect')\n",
              "        .attr('height', 10)\n",
              "        .attr(\n",
              "            'x',\n",
              "            (d, i) =>\n",
              "                (cusum[i] * plot_width + left_margin + options.node_padding))\n",
              "        .attr('width', (d, i) => d * plot_width)\n",
              "        .style('fill', (d, i) => d3.schemeSet1[i]);\n",
              "\n",
              "    const num_examples =\n",
              "        output.content.append('g')\n",
              "            .attr('transform', `translate(0,${output.vertical_offset})`)\n",
              "            .append('text')\n",
              "            .attr('x', options.node_x_size - options.node_padding)\n",
              "            .attr('alignment-baseline', 'hanging')\n",
              "            .attr('text-anchor', 'end')\n",
              "            .text(`(${value.num_examples})`);\n",
              "\n",
              "    const distribution_details = d3.create('ul');\n",
              "    distribution_details.selectAll('li')\n",
              "        .data(value.distribution)\n",
              "        .join('li')\n",
              "        .append('span')\n",
              "        .text(\n",
              "            (d, i) =>\n",
              "                'class ' + i + ': ' + d3.format('.3%')(value.distribution[i]));\n",
              "\n",
              "    add_tooltip(options, distribution_plot, () => distribution_details.html());\n",
              "    add_tooltip(options, num_examples, () => 'Number of examples');\n",
              "\n",
              "    output.vertical_offset += 10;\n",
              "    return;\n",
              "  }\n",
              "\n",
              "  if (value.type === 'REGRESSION') {\n",
              "    display_node_text(\n",
              "        options,\n",
              "        'value: ' + d3.format('r')(value.value) + ` (` +\n",
              "            d3.format('.6')(value.num_examples) + `)`,\n",
              "        output);\n",
              "    return;\n",
              "  }\n",
              "\n",
              "  display_node_text(options, `Non supported value ${value.type}`, output);\n",
              "}\n",
              "\n",
              "/**\n",
              " * Adds an explanation inside of a node.\n",
              " * @param {!options} options Dictionary of configurations.\n",
              " * @param {!explanation} explanation Explanation to display.\n",
              " * @param {!output} output Output display accumulator.\n",
              " */\n",
              "function display_explanation(options, explanation, output) {\n",
              "  // Margin before the explanation.\n",
              "  output.vertical_offset += 10;\n",
              "\n",
              "  display_node_text(\n",
              "      options, `Non supported explanation ${explanation.type}`, output);\n",
              "}\n",
              "\n",
              "\n",
              "/**\n",
              " * Draw the edges of the tree.\n",
              " * @param {!options} options Dictionary of configurations.\n",
              " * @param {!graph} graph D3 search handle containing the graph.\n",
              " * @param {!tree_struct} tree_struct Structure of the tree (node placement,\n",
              " *     data, etc.).\n",
              " */\n",
              "function display_edges(options, graph, tree_struct) {\n",
              "  // Draw an edge between a parent and a child node with a bezier.\n",
              "  function draw_single_edge(d) {\n",
              "    return 'M' + (d.source.y + options.node_x_size) + ',' + d.source.x + ' C' +\n",
              "        (d.source.y + options.node_x_size + options.edge_rounding) + ',' +\n",
              "        d.source.x + ' ' + (d.target.y - options.edge_rounding) + ',' +\n",
              "        d.target.x + ' ' + d.target.y + ',' + d.target.x;\n",
              "  }\n",
              "\n",
              "  graph.append('g')\n",
              "      .attr('fill', 'none')\n",
              "      .attr('stroke-width', 1.2)\n",
              "      .selectAll('path')\n",
              "      .data(tree_struct.links())\n",
              "      .join('path')\n",
              "      .attr('d', draw_single_edge)\n",
              "      .attr(\n",
              "          'stroke', d => (d.target === d.source.children[0]) ? '#0F0' : '#F00');\n",
              "}\n",
              "\n",
              "display_tree({\"margin\": 10, \"node_x_size\": 160, \"node_y_size\": 28, \"node_x_offset\": 180, \"node_y_offset\": 33, \"font_size\": 10, \"edge_rounding\": 20, \"node_padding\": 2, \"show_plot_bounding_box\": false}, {\"value\": {\"type\": \"PROBABILITY\", \"distribution\": [0.46153846153846156, 0.3438914027149321, 0.19457013574660634], \"num_examples\": 221.0}, \"condition\": {\"type\": \"NUMERICAL_IS_HIGHER_THAN\", \"attribute\": \"bill_length_mm\", \"threshold\": 43.25}, \"children\": [{\"value\": {\"type\": \"PROBABILITY\", \"distribution\": [0.05042016806722689, 0.5966386554621849, 0.35294117647058826], \"num_examples\": 119.0}, \"condition\": {\"type\": \"CATEGORICAL_IS_IN\", \"attribute\": \"island\", \"mask\": [\"Dream\", \"Torgersen\"]}, \"children\": [{\"value\": {\"type\": \"PROBABILITY\", \"distribution\": [0.10638297872340426, 0.0, 0.8936170212765957], \"num_examples\": 47.0}}, {\"value\": {\"type\": \"PROBABILITY\", \"distribution\": [0.013888888888888888, 0.9861111111111112, 0.0], \"num_examples\": 72.0}}]}, {\"value\": {\"type\": \"PROBABILITY\", \"distribution\": [0.9411764705882353, 0.049019607843137254, 0.00980392156862745], \"num_examples\": 102.0}, \"condition\": {\"type\": \"NUMERICAL_IS_HIGHER_THAN\", \"attribute\": \"flipper_length_mm\", \"threshold\": 207.0}, \"children\": [{\"value\": {\"type\": \"PROBABILITY\", \"distribution\": [0.0, 1.0, 0.0], \"num_examples\": 5.0}}, {\"value\": {\"type\": \"PROBABILITY\", \"distribution\": [0.9896907216494846, 0.0, 0.010309278350515464], \"num_examples\": 97.0}}]}]}, \"#tree_plot_0aef01c97aa1475caa49cf8309ac7270\")\n",
              "</script>\n"
            ]
          },
          "metadata": {},
          "execution_count": 19
        }
      ]
    },
    {
      "cell_type": "markdown",
      "source": [
        "## 评价模型\n",
        "用测试集评价模型"
      ],
      "metadata": {
        "id": "U9J_Qh_m7TmT"
      }
    },
    {
      "cell_type": "markdown",
      "source": [
        "【说明】：模型的训练和测试准确率，由于决策树没有损失的概念，因此 Keras evaluate 函数返回的“损失”始终为零。\n",
        "\n"
      ],
      "metadata": {
        "id": "9AZFwAyCQJy2"
      }
    },
    {
      "cell_type": "code",
      "source": [
        "model.compile(\"accuracy\")\n",
        "print(\"Train evaluation: \", model.evaluate(train_ds, return_dict=True))\n",
        "\n",
        "\n",
        "print(\"Test evaluation: \", model.evaluate(test_ds, return_dict=True))"
      ],
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "id": "NvDvhxmJQdEO",
        "outputId": "7149e396-f6cf-4f98-ee93-f68dab4e2040"
      },
      "execution_count": 21,
      "outputs": [
        {
          "output_type": "stream",
          "name": "stdout",
          "text": [
            "1/1 [==============================] - 0s 157ms/step - loss: 0.0000e+00 - accuracy: 0.9600\n"
          ]
        },
        {
          "output_type": "stream",
          "name": "stderr",
          "text": [
            "WARNING:tensorflow:5 out of the last 5 calls to <function InferenceCoreModel.make_test_function.<locals>.test_function at 0x7fd257a07af0> triggered tf.function retracing. Tracing is expensive and the excessive number of tracings could be due to (1) creating @tf.function repeatedly in a loop, (2) passing tensors with different shapes, (3) passing Python objects instead of tensors. For (1), please define your @tf.function outside of the loop. For (2), @tf.function has reduce_retracing=True option that can avoid unnecessary retracing. For (3), please refer to https://www.tensorflow.org/guide/function#controlling_retracing and https://www.tensorflow.org/api_docs/python/tf/function for  more details.\n"
          ]
        },
        {
          "output_type": "stream",
          "name": "stdout",
          "text": [
            "Train evaluation:  {'loss': 0.0, 'accuracy': 0.9599999785423279}\n",
            "1/1 [==============================] - 0s 100ms/step - loss: 0.0000e+00 - accuracy: 0.9787\n",
            "Test evaluation:  {'loss': 0.0, 'accuracy': 0.978723406791687}\n"
          ]
        }
      ]
    },
    {
      "cell_type": "markdown",
      "source": [
        "将模型导出为SavedModel格式以便以后重用，例如TensorFlow Serving。"
      ],
      "metadata": {
        "id": "PY3iKXbi8C5b"
      }
    },
    {
      "cell_type": "code",
      "source": [
        "model.save(\"/tmp/my_saved_model\")"
      ],
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "id": "jo6DVrC28KS7",
        "outputId": "0c73be3c-6aab-4970-8bf0-c95555e9dceb"
      },
      "execution_count": 23,
      "outputs": [
        {
          "output_type": "stream",
          "name": "stderr",
          "text": [
            "WARNING:absl:Found untraced functions such as call_get_leaves, _update_step_xla while saving (showing 2 of 2). These functions will not be directly callable after loading.\n"
          ]
        }
      ]
    },
    {
      "cell_type": "markdown",
      "source": [
        "##  Keras 调谐器优化超参数\n",
        "以下代码会优化两个参数：\n",
        "* 条件节点中的示例数下限 (min_examples)\n",
        "* 用于剪枝验证的训练数据集所占的比率 (validation_ratio)\n",
        "\n",
        "由于我们不知道这些参数的最佳值，因此我们提供各种选项供调谐器尝试。我们分别为 min_examples 和 validation_ratio 选择了四个可能的值。增加候选超参数值的数量会增加训练更好模型的几率，但也会增加训练时间。"
      ],
      "metadata": {
        "id": "KR4xIkVoSGkO"
      }
    },
    {
      "cell_type": "code",
      "source": [
        "!pip install keras-tuner"
      ],
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "id": "mZpc67KlU0RF",
        "outputId": "0567ada8-7fb9-401b-a011-33cc05311635"
      },
      "execution_count": 22,
      "outputs": [
        {
          "output_type": "stream",
          "name": "stdout",
          "text": [
            "Looking in indexes: https://pypi.org/simple, https://us-python.pkg.dev/colab-wheels/public/simple/\n",
            "Collecting keras-tuner\n",
            "  Downloading keras_tuner-1.3.5-py3-none-any.whl (176 kB)\n",
            "\u001b[2K     \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m176.1/176.1 kB\u001b[0m \u001b[31m4.4 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n",
            "\u001b[?25hRequirement already satisfied: requests in /usr/local/lib/python3.9/dist-packages (from keras-tuner) (2.27.1)\n",
            "Collecting kt-legacy\n",
            "  Downloading kt_legacy-1.0.5-py3-none-any.whl (9.6 kB)\n",
            "Requirement already satisfied: packaging in /usr/local/lib/python3.9/dist-packages (from keras-tuner) (23.1)\n",
            "Requirement already satisfied: urllib3<1.27,>=1.21.1 in /usr/local/lib/python3.9/dist-packages (from requests->keras-tuner) (1.26.15)\n",
            "Requirement already satisfied: certifi>=2017.4.17 in /usr/local/lib/python3.9/dist-packages (from requests->keras-tuner) (2022.12.7)\n",
            "Requirement already satisfied: charset-normalizer~=2.0.0 in /usr/local/lib/python3.9/dist-packages (from requests->keras-tuner) (2.0.12)\n",
            "Requirement already satisfied: idna<4,>=2.5 in /usr/local/lib/python3.9/dist-packages (from requests->keras-tuner) (3.4)\n",
            "Installing collected packages: kt-legacy, keras-tuner\n",
            "Successfully installed keras-tuner-1.3.5 kt-legacy-1.0.5\n"
          ]
        }
      ]
    },
    {
      "cell_type": "code",
      "source": [
        "import keras_tuner as kt\n",
        "\n",
        "def build_model(hp):\n",
        "  model = tfdf.keras.CartModel(\n",
        "      min_examples=hp.Choice(\"min_examples\",\n",
        "          # 为“min_examples”超参数尝试四个可能的值\n",
        "          # Min_examples=10会限制决策树的增长，\n",
        "          # 而min_examples=1将导致更深入的决策树。\n",
        "         [1, 2, 5, 10]),\n",
        "      validation_ratio=hp.Choice(\"validation_ratio\",\n",
        "         # “validation_ratio”超参数的三个可能值\n",
        "         [0.0, 0.05, 0.10]),\n",
        "      )\n",
        "  model.compile(\"accuracy\")\n",
        "  return model\n",
        "\n",
        "tuner = kt.RandomSearch(\n",
        "    build_model,\n",
        "    objective=\"val_accuracy\",\n",
        "    max_trials=10,\n",
        "    directory=\"/tmp/tuner\",\n",
        "    project_name=\"tune_cart\")\n",
        "\n",
        "tuner.search(x=train_ds, validation_data=test_ds)\n",
        "best_model = tuner.get_best_models()[0]\n",
        "\n",
        "print(\"Best hyperparameters: \", tuner.get_best_hyperparameters()[0].values)"
      ],
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "id": "Wri1NzF_U-Nt",
        "outputId": "3aeb7e6a-e668-4726-f783-9a577d1d5330"
      },
      "execution_count": 24,
      "outputs": [
        {
          "output_type": "stream",
          "name": "stdout",
          "text": [
            "Trial 10 Complete [00h 00m 01s]\n",
            "val_accuracy: 0.978723406791687\n",
            "\n",
            "Best val_accuracy So Far: 1.0\n",
            "Total elapsed time: 00h 00m 14s\n",
            "Use /tmp/tmpdgrhf6d8 as temporary training directory\n",
            "Best hyperparameters:  {'min_examples': 5, 'validation_ratio': 0.0}\n"
          ]
        }
      ]
    },
    {
      "cell_type": "markdown",
      "source": [
        "使用这些优化的超参数重新训练和评估模型："
      ],
      "metadata": {
        "id": "zuKj5HhuVnKd"
      }
    },
    {
      "cell_type": "code",
      "source": [
        "model = tfdf.keras.CartModel(min_examples=2, validation_ratio=0.0)\n",
        "model.fit(train_ds)\n",
        "\n",
        "model.compile(\"accuracy\")\n",
        "print(\"Test evaluation: \", model.evaluate(test_ds, return_dict=True))\n",
        "# >> Test evaluation:  {'loss': 0.0, 'accuracy': 1.0}"
      ],
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "id": "ryfjSf3cVsBc",
        "outputId": "0880adc5-844a-4b1a-818a-ec7f9530642b"
      },
      "execution_count": 25,
      "outputs": [
        {
          "output_type": "stream",
          "name": "stdout",
          "text": [
            "Use /tmp/tmprffgdg52 as temporary training directory\n",
            "Reading training dataset...\n",
            "Training dataset read in 0:00:00.221963. Found 250 examples.\n",
            "Training model...\n",
            "Model trained in 0:00:00.031410\n",
            "Compiling model...\n",
            "Model compiled.\n",
            "1/1 [==============================] - 0s 167ms/step - loss: 0.0000e+00 - accuracy: 1.0000\n",
            "Test evaluation:  {'loss': 0.0, 'accuracy': 1.0}\n"
          ]
        }
      ]
    },
    {
      "cell_type": "code",
      "source": [
        "# 可视化\n",
        "tfdf.model_plotter.plot_model_in_colab(model, max_depth=10)"
      ],
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/",
          "height": 440
        },
        "id": "R8-2rZ4fV8l1",
        "outputId": "e7cd8c96-a5c3-4c3d-e045-62b67b01839f"
      },
      "execution_count": 26,
      "outputs": [
        {
          "output_type": "execute_result",
          "data": {
            "text/plain": [
              "<IPython.core.display.HTML object>"
            ],
            "text/html": [
              "\n",
              "<script src=\"https://d3js.org/d3.v6.min.js\"></script>\n",
              "<div id=\"tree_plot_ddce08cdf889462dba16377fde64521c\"></div>\n",
              "<script>\n",
              "/*\n",
              " * Copyright 2021 Google LLC.\n",
              " * Licensed under the Apache License, Version 2.0 (the \"License\");\n",
              " * you may not use this file except in compliance with the License.\n",
              " * You may obtain a copy of the License at\n",
              " *\n",
              " *     https://www.apache.org/licenses/LICENSE-2.0\n",
              " *\n",
              " * Unless required by applicable law or agreed to in writing, software\n",
              " * distributed under the License is distributed on an \"AS IS\" BASIS,\n",
              " * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n",
              " * See the License for the specific language governing permissions and\n",
              " * limitations under the License.\n",
              " */\n",
              "\n",
              "/**\n",
              " *  Plotting of decision trees generated by TF-DF.\n",
              " *\n",
              " *  A tree is a recursive structure of node objects.\n",
              " *  A node contains one or more of the following components:\n",
              " *\n",
              " *    - A value: Representing the output of the node. If the node is not a leaf,\n",
              " *      the value is only present for analysis i.e. it is not used for\n",
              " *      predictions.\n",
              " *\n",
              " *    - A condition : For non-leaf nodes, the condition (also known as split)\n",
              " *      defines a binary test to branch to the positive or negative child.\n",
              " *\n",
              " *    - An explanation: Generally a plot showing the relation between the label\n",
              " *      and the condition to give insights about the effect of the condition.\n",
              " *\n",
              " *    - Two children : For non-leaf nodes, the children nodes. The first\n",
              " *      children (i.e. \"node.children[0]\") is the negative children (drawn in\n",
              " *      red). The second children is the positive one (drawn in green).\n",
              " *\n",
              " */\n",
              "\n",
              "/**\n",
              " * Plots a single decision tree into a DOM element.\n",
              " * @param {!options} options Dictionary of configurations.\n",
              " * @param {!tree} raw_tree Recursive tree structure.\n",
              " * @param {string} canvas_id Id of the output dom element.\n",
              " */\n",
              "function display_tree(options, raw_tree, canvas_id) {\n",
              "  console.log(options);\n",
              "\n",
              "  // Determine the node placement.\n",
              "  const tree_struct = d3.tree().nodeSize(\n",
              "      [options.node_y_offset, options.node_x_offset])(d3.hierarchy(raw_tree));\n",
              "\n",
              "  // Boundaries of the node placement.\n",
              "  let x_min = Infinity;\n",
              "  let x_max = -x_min;\n",
              "  let y_min = Infinity;\n",
              "  let y_max = -x_min;\n",
              "\n",
              "  tree_struct.each(d => {\n",
              "    if (d.x > x_max) x_max = d.x;\n",
              "    if (d.x < x_min) x_min = d.x;\n",
              "    if (d.y > y_max) y_max = d.y;\n",
              "    if (d.y < y_min) y_min = d.y;\n",
              "  });\n",
              "\n",
              "  // Size of the plot.\n",
              "  const width = y_max - y_min + options.node_x_size + options.margin * 2;\n",
              "  const height = x_max - x_min + options.node_y_size + options.margin * 2 +\n",
              "      options.node_y_offset - options.node_y_size;\n",
              "\n",
              "  const plot = d3.select(canvas_id);\n",
              "\n",
              "  // Tool tip\n",
              "  options.tooltip = plot.append('div')\n",
              "                        .attr('width', 100)\n",
              "                        .attr('height', 100)\n",
              "                        .style('padding', '4px')\n",
              "                        .style('background', '#fff')\n",
              "                        .style('box-shadow', '4px 4px 0px rgba(0,0,0,0.1)')\n",
              "                        .style('border', '1px solid black')\n",
              "                        .style('font-family', 'sans-serif')\n",
              "                        .style('font-size', options.font_size)\n",
              "                        .style('position', 'absolute')\n",
              "                        .style('z-index', '10')\n",
              "                        .attr('pointer-events', 'none')\n",
              "                        .style('display', 'none');\n",
              "\n",
              "  // Create canvas\n",
              "  const svg = plot.append('svg').attr('width', width).attr('height', height);\n",
              "  const graph =\n",
              "      svg.style('overflow', 'visible')\n",
              "          .append('g')\n",
              "          .attr('font-family', 'sans-serif')\n",
              "          .attr('font-size', options.font_size)\n",
              "          .attr(\n",
              "              'transform',\n",
              "              () => `translate(${options.margin},${\n",
              "                  - x_min + options.node_y_offset / 2 + options.margin})`);\n",
              "\n",
              "  // Plot bounding box.\n",
              "  if (options.show_plot_bounding_box) {\n",
              "    svg.append('rect')\n",
              "        .attr('width', width)\n",
              "        .attr('height', height)\n",
              "        .attr('fill', 'none')\n",
              "        .attr('stroke-width', 1.0)\n",
              "        .attr('stroke', 'black');\n",
              "  }\n",
              "\n",
              "  // Draw the edges.\n",
              "  display_edges(options, graph, tree_struct);\n",
              "\n",
              "  // Draw the nodes.\n",
              "  display_nodes(options, graph, tree_struct);\n",
              "}\n",
              "\n",
              "/**\n",
              " * Draw the nodes of the tree.\n",
              " * @param {!options} options Dictionary of configurations.\n",
              " * @param {!graph} graph D3 search handle containing the graph.\n",
              " * @param {!tree_struct} tree_struct Structure of the tree (node placement,\n",
              " *     data, etc.).\n",
              " */\n",
              "function display_nodes(options, graph, tree_struct) {\n",
              "  const nodes = graph.append('g')\n",
              "                    .selectAll('g')\n",
              "                    .data(tree_struct.descendants())\n",
              "                    .join('g')\n",
              "                    .attr('transform', d => `translate(${d.y},${d.x})`);\n",
              "\n",
              "  nodes.append('rect')\n",
              "      .attr('x', 0.5)\n",
              "      .attr('y', 0.5)\n",
              "      .attr('width', options.node_x_size)\n",
              "      .attr('height', options.node_y_size)\n",
              "      .attr('stroke', 'lightgrey')\n",
              "      .attr('stroke-width', 1)\n",
              "      .attr('fill', 'white')\n",
              "      .attr('y', -options.node_y_size / 2);\n",
              "\n",
              "  // Brackets on the right of condition nodes without children.\n",
              "  non_leaf_node_without_children =\n",
              "      nodes.filter(node => node.data.condition != null && node.children == null)\n",
              "          .append('g')\n",
              "          .attr('transform', `translate(${options.node_x_size},0)`);\n",
              "\n",
              "  non_leaf_node_without_children.append('path')\n",
              "      .attr('d', 'M0,0 C 10,0 0,10 10,10')\n",
              "      .attr('fill', 'none')\n",
              "      .attr('stroke-width', 1.0)\n",
              "      .attr('stroke', '#F00');\n",
              "\n",
              "  non_leaf_node_without_children.append('path')\n",
              "      .attr('d', 'M0,0 C 10,0 0,-10 10,-10')\n",
              "      .attr('fill', 'none')\n",
              "      .attr('stroke-width', 1.0)\n",
              "      .attr('stroke', '#0F0');\n",
              "\n",
              "  const node_content = nodes.append('g').attr(\n",
              "      'transform',\n",
              "      `translate(0,${options.node_padding - options.node_y_size / 2})`);\n",
              "\n",
              "  node_content.append(node => create_node_element(options, node));\n",
              "}\n",
              "\n",
              "/**\n",
              " * Creates the D3 content for a single node.\n",
              " * @param {!options} options Dictionary of configurations.\n",
              " * @param {!node} node Node to draw.\n",
              " * @return {!d3} D3 content.\n",
              " */\n",
              "function create_node_element(options, node) {\n",
              "  // Output accumulator.\n",
              "  let output = {\n",
              "    // Content to draw.\n",
              "    content: d3.create('svg:g'),\n",
              "    // Vertical offset to the next element to draw.\n",
              "    vertical_offset: 0\n",
              "  };\n",
              "\n",
              "  // Conditions.\n",
              "  if (node.data.condition != null) {\n",
              "    display_condition(options, node.data.condition, output);\n",
              "  }\n",
              "\n",
              "  // Values.\n",
              "  if (node.data.value != null) {\n",
              "    display_value(options, node.data.value, output);\n",
              "  }\n",
              "\n",
              "  // Explanations.\n",
              "  if (node.data.explanation != null) {\n",
              "    display_explanation(options, node.data.explanation, output);\n",
              "  }\n",
              "\n",
              "  return output.content.node();\n",
              "}\n",
              "\n",
              "\n",
              "/**\n",
              " * Adds a single line of text inside of a node.\n",
              " * @param {!options} options Dictionary of configurations.\n",
              " * @param {string} text Text to display.\n",
              " * @param {!output} output Output display accumulator.\n",
              " */\n",
              "function display_node_text(options, text, output) {\n",
              "  output.content.append('text')\n",
              "      .attr('x', options.node_padding)\n",
              "      .attr('y', output.vertical_offset)\n",
              "      .attr('alignment-baseline', 'hanging')\n",
              "      .text(text);\n",
              "  output.vertical_offset += 10;\n",
              "}\n",
              "\n",
              "/**\n",
              " * Adds a single line of text inside of a node with a tooltip.\n",
              " * @param {!options} options Dictionary of configurations.\n",
              " * @param {string} text Text to display.\n",
              " * @param {string} tooltip Text in the Tooltip.\n",
              " * @param {!output} output Output display accumulator.\n",
              " */\n",
              "function display_node_text_with_tooltip(options, text, tooltip, output) {\n",
              "  const item = output.content.append('text')\n",
              "                   .attr('x', options.node_padding)\n",
              "                   .attr('alignment-baseline', 'hanging')\n",
              "                   .text(text);\n",
              "\n",
              "  add_tooltip(options, item, () => tooltip);\n",
              "  output.vertical_offset += 10;\n",
              "}\n",
              "\n",
              "/**\n",
              " * Adds a tooltip to a dom element.\n",
              " * @param {!options} options Dictionary of configurations.\n",
              " * @param {!dom} target Dom element to equip with a tooltip.\n",
              " * @param {!func} get_content Generates the html content of the tooltip.\n",
              " */\n",
              "function add_tooltip(options, target, get_content) {\n",
              "  function show(d) {\n",
              "    options.tooltip.style('display', 'block');\n",
              "    options.tooltip.html(get_content());\n",
              "  }\n",
              "\n",
              "  function hide(d) {\n",
              "    options.tooltip.style('display', 'none');\n",
              "  }\n",
              "\n",
              "  function move(d) {\n",
              "    options.tooltip.style('display', 'block');\n",
              "    options.tooltip.style('left', (d.pageX + 5) + 'px');\n",
              "    options.tooltip.style('top', d.pageY + 'px');\n",
              "  }\n",
              "\n",
              "  target.on('mouseover', show);\n",
              "  target.on('mouseout', hide);\n",
              "  target.on('mousemove', move);\n",
              "}\n",
              "\n",
              "/**\n",
              " * Adds a condition inside of a node.\n",
              " * @param {!options} options Dictionary of configurations.\n",
              " * @param {!condition} condition Condition to display.\n",
              " * @param {!output} output Output display accumulator.\n",
              " */\n",
              "function display_condition(options, condition, output) {\n",
              "  threshold_format = d3.format('r');\n",
              "\n",
              "  if (condition.type === 'IS_MISSING') {\n",
              "    display_node_text(options, `${condition.attribute} is missing`, output);\n",
              "    return;\n",
              "  }\n",
              "\n",
              "  if (condition.type === 'IS_TRUE') {\n",
              "    display_node_text(options, `${condition.attribute} is true`, output);\n",
              "    return;\n",
              "  }\n",
              "\n",
              "  if (condition.type === 'NUMERICAL_IS_HIGHER_THAN') {\n",
              "    format = d3.format('r');\n",
              "    display_node_text(\n",
              "        options,\n",
              "        `${condition.attribute} >= ${threshold_format(condition.threshold)}`,\n",
              "        output);\n",
              "    return;\n",
              "  }\n",
              "\n",
              "  if (condition.type === 'CATEGORICAL_IS_IN') {\n",
              "    display_node_text_with_tooltip(\n",
              "        options, `${condition.attribute} in [...]`,\n",
              "        `${condition.attribute} in [${condition.mask}]`, output);\n",
              "    return;\n",
              "  }\n",
              "\n",
              "  if (condition.type === 'CATEGORICAL_SET_CONTAINS') {\n",
              "    display_node_text_with_tooltip(\n",
              "        options, `${condition.attribute} intersect [...]`,\n",
              "        `${condition.attribute} intersect [${condition.mask}]`, output);\n",
              "    return;\n",
              "  }\n",
              "\n",
              "  if (condition.type === 'NUMERICAL_SPARSE_OBLIQUE') {\n",
              "    display_node_text_with_tooltip(\n",
              "        options, `Sparse oblique split...`,\n",
              "        `[${condition.attributes}]*[${condition.weights}]>=${\n",
              "            threshold_format(condition.threshold)}`,\n",
              "        output);\n",
              "    return;\n",
              "  }\n",
              "\n",
              "  display_node_text(\n",
              "      options, `Non supported condition ${condition.type}`, output);\n",
              "}\n",
              "\n",
              "/**\n",
              " * Adds a value inside of a node.\n",
              " * @param {!options} options Dictionary of configurations.\n",
              " * @param {!value} value Value to display.\n",
              " * @param {!output} output Output display accumulator.\n",
              " */\n",
              "function display_value(options, value, output) {\n",
              "  if (value.type === 'PROBABILITY') {\n",
              "    const left_margin = 0;\n",
              "    const right_margin = 50;\n",
              "    const plot_width = options.node_x_size - options.node_padding * 2 -\n",
              "        left_margin - right_margin;\n",
              "\n",
              "    let cusum = Array.from(d3.cumsum(value.distribution));\n",
              "    cusum.unshift(0);\n",
              "    const distribution_plot = output.content.append('g').attr(\n",
              "        'transform', `translate(0,${output.vertical_offset + 0.5})`);\n",
              "\n",
              "    distribution_plot.selectAll('rect')\n",
              "        .data(value.distribution)\n",
              "        .join('rect')\n",
              "        .attr('height', 10)\n",
              "        .attr(\n",
              "            'x',\n",
              "            (d, i) =>\n",
              "                (cusum[i] * plot_width + left_margin + options.node_padding))\n",
              "        .attr('width', (d, i) => d * plot_width)\n",
              "        .style('fill', (d, i) => d3.schemeSet1[i]);\n",
              "\n",
              "    const num_examples =\n",
              "        output.content.append('g')\n",
              "            .attr('transform', `translate(0,${output.vertical_offset})`)\n",
              "            .append('text')\n",
              "            .attr('x', options.node_x_size - options.node_padding)\n",
              "            .attr('alignment-baseline', 'hanging')\n",
              "            .attr('text-anchor', 'end')\n",
              "            .text(`(${value.num_examples})`);\n",
              "\n",
              "    const distribution_details = d3.create('ul');\n",
              "    distribution_details.selectAll('li')\n",
              "        .data(value.distribution)\n",
              "        .join('li')\n",
              "        .append('span')\n",
              "        .text(\n",
              "            (d, i) =>\n",
              "                'class ' + i + ': ' + d3.format('.3%')(value.distribution[i]));\n",
              "\n",
              "    add_tooltip(options, distribution_plot, () => distribution_details.html());\n",
              "    add_tooltip(options, num_examples, () => 'Number of examples');\n",
              "\n",
              "    output.vertical_offset += 10;\n",
              "    return;\n",
              "  }\n",
              "\n",
              "  if (value.type === 'REGRESSION') {\n",
              "    display_node_text(\n",
              "        options,\n",
              "        'value: ' + d3.format('r')(value.value) + ` (` +\n",
              "            d3.format('.6')(value.num_examples) + `)`,\n",
              "        output);\n",
              "    return;\n",
              "  }\n",
              "\n",
              "  display_node_text(options, `Non supported value ${value.type}`, output);\n",
              "}\n",
              "\n",
              "/**\n",
              " * Adds an explanation inside of a node.\n",
              " * @param {!options} options Dictionary of configurations.\n",
              " * @param {!explanation} explanation Explanation to display.\n",
              " * @param {!output} output Output display accumulator.\n",
              " */\n",
              "function display_explanation(options, explanation, output) {\n",
              "  // Margin before the explanation.\n",
              "  output.vertical_offset += 10;\n",
              "\n",
              "  display_node_text(\n",
              "      options, `Non supported explanation ${explanation.type}`, output);\n",
              "}\n",
              "\n",
              "\n",
              "/**\n",
              " * Draw the edges of the tree.\n",
              " * @param {!options} options Dictionary of configurations.\n",
              " * @param {!graph} graph D3 search handle containing the graph.\n",
              " * @param {!tree_struct} tree_struct Structure of the tree (node placement,\n",
              " *     data, etc.).\n",
              " */\n",
              "function display_edges(options, graph, tree_struct) {\n",
              "  // Draw an edge between a parent and a child node with a bezier.\n",
              "  function draw_single_edge(d) {\n",
              "    return 'M' + (d.source.y + options.node_x_size) + ',' + d.source.x + ' C' +\n",
              "        (d.source.y + options.node_x_size + options.edge_rounding) + ',' +\n",
              "        d.source.x + ' ' + (d.target.y - options.edge_rounding) + ',' +\n",
              "        d.target.x + ' ' + d.target.y + ',' + d.target.x;\n",
              "  }\n",
              "\n",
              "  graph.append('g')\n",
              "      .attr('fill', 'none')\n",
              "      .attr('stroke-width', 1.2)\n",
              "      .selectAll('path')\n",
              "      .data(tree_struct.links())\n",
              "      .join('path')\n",
              "      .attr('d', draw_single_edge)\n",
              "      .attr(\n",
              "          'stroke', d => (d.target === d.source.children[0]) ? '#0F0' : '#F00');\n",
              "}\n",
              "\n",
              "display_tree({\"margin\": 10, \"node_x_size\": 160, \"node_y_size\": 28, \"node_x_offset\": 180, \"node_y_offset\": 33, \"font_size\": 10, \"edge_rounding\": 20, \"node_padding\": 2, \"show_plot_bounding_box\": false}, {\"value\": {\"type\": \"PROBABILITY\", \"distribution\": [0.444, 0.368, 0.188], \"num_examples\": 250.0}, \"condition\": {\"type\": \"NUMERICAL_IS_HIGHER_THAN\", \"attribute\": \"bill_length_mm\", \"threshold\": 42.400001525878906}, \"children\": [{\"value\": {\"type\": \"PROBABILITY\", \"distribution\": [0.08163265306122448, 0.6054421768707483, 0.3129251700680272], \"num_examples\": 147.0}, \"condition\": {\"type\": \"CATEGORICAL_IS_IN\", \"attribute\": \"island\", \"mask\": [\"Dream\", \"Torgersen\"]}, \"children\": [{\"value\": {\"type\": \"PROBABILITY\", \"distribution\": [0.16363636363636364, 0.0, 0.8363636363636363], \"num_examples\": 55.0}, \"condition\": {\"type\": \"NUMERICAL_IS_HIGHER_THAN\", \"attribute\": \"body_mass_g\", \"threshold\": 4075.0}, \"children\": [{\"value\": {\"type\": \"PROBABILITY\", \"distribution\": [0.5, 0.0, 0.5], \"num_examples\": 14.0}, \"condition\": {\"type\": \"NUMERICAL_IS_HIGHER_THAN\", \"attribute\": \"bill_length_mm\", \"threshold\": 45.900001525878906}, \"children\": [{\"value\": {\"type\": \"PROBABILITY\", \"distribution\": [0.125, 0.0, 0.875], \"num_examples\": 8.0}, \"condition\": {\"type\": \"NUMERICAL_IS_HIGHER_THAN\", \"attribute\": \"bill_length_mm\", \"threshold\": 47.599998474121094}, \"children\": [{\"value\": {\"type\": \"PROBABILITY\", \"distribution\": [0.0, 0.0, 1.0], \"num_examples\": 6.0}}, {\"value\": {\"type\": \"PROBABILITY\", \"distribution\": [0.5, 0.0, 0.5], \"num_examples\": 2.0}}]}, {\"value\": {\"type\": \"PROBABILITY\", \"distribution\": [1.0, 0.0, 0.0], \"num_examples\": 6.0}}]}, {\"value\": {\"type\": \"PROBABILITY\", \"distribution\": [0.04878048780487805, 0.0, 0.9512195121951219], \"num_examples\": 41.0}, \"condition\": {\"type\": \"NUMERICAL_IS_HIGHER_THAN\", \"attribute\": \"bill_length_mm\", \"threshold\": 44.75}, \"children\": [{\"value\": {\"type\": \"PROBABILITY\", \"distribution\": [0.0, 0.0, 1.0], \"num_examples\": 35.0}}, {\"value\": {\"type\": \"PROBABILITY\", \"distribution\": [0.3333333333333333, 0.0, 0.6666666666666666], \"num_examples\": 6.0}, \"condition\": {\"type\": \"CATEGORICAL_IS_IN\", \"attribute\": \"sex\", \"mask\": [\"male\"]}, \"children\": [{\"value\": {\"type\": \"PROBABILITY\", \"distribution\": [1.0, 0.0, 0.0], \"num_examples\": 2.0}}, {\"value\": {\"type\": \"PROBABILITY\", \"distribution\": [0.0, 0.0, 1.0], \"num_examples\": 4.0}}]}]}]}, {\"value\": {\"type\": \"PROBABILITY\", \"distribution\": [0.03260869565217391, 0.967391304347826, 0.0], \"num_examples\": 92.0}, \"condition\": {\"type\": \"NUMERICAL_IS_HIGHER_THAN\", \"attribute\": \"flipper_length_mm\", \"threshold\": 199.09475708007812}, \"children\": [{\"value\": {\"type\": \"PROBABILITY\", \"distribution\": [0.0, 1.0, 0.0], \"num_examples\": 89.0}}, {\"value\": {\"type\": \"PROBABILITY\", \"distribution\": [1.0, 0.0, 0.0], \"num_examples\": 3.0}}]}]}, {\"value\": {\"type\": \"PROBABILITY\", \"distribution\": [0.9611650485436893, 0.02912621359223301, 0.009708737864077669], \"num_examples\": 103.0}, \"condition\": {\"type\": \"NUMERICAL_IS_HIGHER_THAN\", \"attribute\": \"bill_depth_mm\", \"threshold\": 15.100000381469727}, \"children\": [{\"value\": {\"type\": \"PROBABILITY\", \"distribution\": [0.99, 0.0, 0.01], \"num_examples\": 100.0}, \"condition\": {\"type\": \"NUMERICAL_IS_HIGHER_THAN\", \"attribute\": \"bill_depth_mm\", \"threshold\": 16.650001525878906}, \"children\": [{\"value\": {\"type\": \"PROBABILITY\", \"distribution\": [1.0, 0.0, 0.0], \"num_examples\": 94.0}}, {\"value\": {\"type\": \"PROBABILITY\", \"distribution\": [0.8333333333333334, 0.0, 0.16666666666666666], \"num_examples\": 6.0}, \"condition\": {\"type\": \"NUMERICAL_IS_HIGHER_THAN\", \"attribute\": \"bill_length_mm\", \"threshold\": 37.54999923706055}, \"children\": [{\"value\": {\"type\": \"PROBABILITY\", \"distribution\": [0.5, 0.0, 0.5], \"num_examples\": 2.0}}, {\"value\": {\"type\": \"PROBABILITY\", \"distribution\": [1.0, 0.0, 0.0], \"num_examples\": 4.0}}]}]}, {\"value\": {\"type\": \"PROBABILITY\", \"distribution\": [0.0, 1.0, 0.0], \"num_examples\": 3.0}}]}]}, \"#tree_plot_ddce08cdf889462dba16377fde64521c\")\n",
              "</script>\n"
            ]
          },
          "metadata": {},
          "execution_count": 26
        }
      ]
    },
    {
      "cell_type": "markdown",
      "source": [
        "正如新的超参数值所预期的那样，此决策树比之前更深，原因如下：\n",
        "\n",
        "* 示例数下限已减少（从 5 个减少到 2 个）。\n",
        "* 验证剪枝已停用 (validation_ratio=0.0)，导致有更多可用的训练示例且不剪枝。"
      ],
      "metadata": {
        "id": "wXYuBgkiWC3d"
      }
    },
    {
      "cell_type": "markdown",
      "source": [
        "## 参考\n",
        "https://www.tensorflow.org/decision_forests/tutorials/beginner_colab#model_structure_and_feature_importance"
      ],
      "metadata": {
        "id": "Rk_f16NhJI33"
      }
    }
  ]
}